﻿#region MIT Licence
/*
 * Copyright (C) 2014 PdfCreative
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
 * and associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom 
 * the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE 
 * AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;

using PdfCreative.Pdf.Writer.Utilities;

namespace PdfCreative.Pdf.Writer.Images
{
    /// <summary>
    /// The JpegImageInfo class encapsulates image information for a JPEG image. It parses a JPEG image file or stream
    /// and retrieves the necessary information. This class inherits from the ImageInfo abstract class.
    /// It is used by the Writer to retrieve information about embedded JPEG images.
    /// </summary>
    public class JpegImageInfo : ImageInfo
    {
        // Private fields
        // ==============
        #region Private fields
        private int _width;
        private int _height;
        private PdfColourSpace _colourSpace;
        #endregion




        // Base class overrides
        // ====================
        #region ImageInfo properties
        /// <summary>
        /// Retrieves the colour space of the image.
        /// </summary>
        public override PdfColourSpace ColourSpace
        {
            get { return _colourSpace; }
        }

        /// <summary>
        /// Retrieves the width of the image.
        /// </summary>
        public override int Width
        {
            get { return _width; }
        }

        /// <summary>
        /// Retrieves the height of the image.
        /// </summary>
        public override int Height
        {
            get { return _height; }
        }
        #endregion

        #region ImageInfo methods
        /// <summary>
        /// Parses the image data found in the specified file.
        /// </summary>
        /// <param name="filename">The file to read the image data from</param>
        /// <exception cref="ImageParseException">An error occurred while reading the image data</exception>
        public override void Parse(string filename)
        {
            if (null == filename) throw new ArgumentNullException("filename");

            Parse(File.OpenRead(filename));
        }

        /// <summary>
        /// Parses the image data in the specified stream.
        /// </summary>
        /// <param name="stream">The stream to read the image data from</param>
        /// <exception cref="ImageParseException">An error occurred while reading the image data</exception>
        public override void Parse(System.IO.Stream stream)
        {
            if (null == stream) throw new ArgumentNullException("stream");

            ParseJpegStream(stream);
        }
        #endregion



        // Private implementation
        // ======================
        #region Private implementation
        /// <summary>
        /// Parses the image data contained within the specified JPEG stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        private void ParseJpegStream(Stream stream)
        {
            Debug.Assert(null != stream);

            try
            {
                // Rewind the stream.
                stream.Seek(0, SeekOrigin.Begin);

                // We open the file and look at the first two bytes. These need to match the JFIF file identifier.
                if (stream.ReadByte() == 0xff && stream.ReadByte() == 0xd8)
                {
                    // Next up we read the JFIF headers. Each of these contains an identifier and the length
                    // of the header. We are looking for the header with ID 192 (c0). This contains the width
                    // and height of the image.
                    while (0xff == stream.ReadByte())
                    {
                        int identifier = stream.ReadByte();
                        long currentPosition = stream.Position;
                        int length = stream.ReadByte() << 8 | stream.ReadByte();

                        switch(identifier)
                        {
                            case 0xc0:
                            case 0xc2:
                                // This is the frame header. This will contain the width and height.
                                stream.Seek(1, SeekOrigin.Current);
                                _height = stream.ReadByte() << 8 | stream.ReadByte();
                                _width = stream.ReadByte() << 8 | stream.ReadByte();
                                break;

                            case 0xee:
                                // This is the Adobe APP14 header. This could contain an 'Adobe'
                                // value that indicates RGB or CMYK data.
                                string header = ReadString(stream);
                                if (String.Equals("Adobe", header, StringComparison.InvariantCultureIgnoreCase))
                                {
                                    // 4 bytes into the 'ADOBE' marker we should find the color space.
                                    stream.Seek(5, SeekOrigin.Current);
                                    int colourSpace = stream.ReadByte();
                                    switch (colourSpace)
                                    {
                                        case 2:
                                            _colourSpace = PdfColourSpace.DeviceCMYK;
                                            break;
                                    }
                                }
                                break;

                            default:
                                break;

                        }

                        // Find the next marker
                        stream.Seek(length + currentPosition-stream.Position, SeekOrigin.Current);

                    }
                }
            }
            // If an I/O error occurs we rethrow this as a JpegImageParseException and include the original exception for reference.
            catch (IOException ex)
            {
                throw new JpegImageParseException("An I/O error occurred while reading JPEG image information from the stream", ex);
            }
        }

        /// <summary>
        /// Reads a string from the specified stream.
        /// </summary>
        /// <param name="stream">The stream</param>
        /// <returns>The string</returns>
        private string ReadString(Stream stream)
        {
            StringBuilder sb = new StringBuilder();

            while (true)
            {
                int value = stream.ReadByte();
                if (value > 0)
                    sb.Append((char)value);
                else
                    break;
            }

            return sb.ToString();
        }
        #endregion

    } // JpegImageInfo class
}
